'
' *  Authors:  Benton Stark
' * 
' *  Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com) 
' *
' * Permission is hereby granted, free of charge, to any person obtaining a copy
' * of this software and associated documentation files (the "Software"), to deal
' * in the Software without restriction, including without limitation the rights
' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
' * copies of the Software, and to permit persons to whom the Software is
' * furnished to do so, subject to the following conditions:
' * 
' * The above copyright notice and this permission notice shall be included in
' * all copies or substantial portions of the Software.
' * 
' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
' * THE SOFTWARE.
' * 
' 


Imports System.Text.RegularExpressions
Imports System.Globalization
Imports System.Text

Namespace FTP
    ''' <summary>
    ''' Generic ftp file and directory LIST listing parser that supports most Unix, Dos, and Windows style FTP 
    ''' directory listings.  A custom parser can be created using the IFtpItemParser interface in the event
    ''' this parser does not suit the needs of a specific FTP server directory format listing.
    ''' </summary>
    Public Class FtpListItemParser
        Implements IFtpItemParser
        ' unix regex expressions
        Private _isUnix As New Regex("(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled)
        Private _unixAttribs As New Regex("(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled)
        Private _unixMonth As New Regex("(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixDay As New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+)\d+", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixYear As New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)(19|20)\d\d", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixTime As New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)\d+:\d\d", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixSize As New Regex("\d+(?=(\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)))", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixName As New Regex("((?<=((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+(19|20)\d\d\s+)|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+\d+:\d\d\s+)).+)", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
        Private _unixSymbLink As New Regex("(?<=\s+->\s+).+", RegexOptions.Compiled)
        Private _unixType As New Regex("(d|l|-|b|c|p|s)(?=(r|w|x|-|t|s){9})", RegexOptions.Compiled)

        ' dos and other expressions
        Private _dosName As Regex = New System.Text.RegularExpressions.Regex("((?<=<DIR>\s+).+)|((?<=\d\d:\d\d\s+).+)|((?<=(\d\d:\d\d(AM|PM|am|pm)\s+\d+\s+)).+)", RegexOptions.Compiled)
        Private _dosDate As Regex = New System.Text.RegularExpressions.Regex("(\d\d-\d\d-\d\d)", RegexOptions.Compiled)
        Private _dosTime As Regex = New System.Text.RegularExpressions.Regex("(\d\d:\d\d\s*(AM|PM|am|pm))|(\d\d:\d\d)", RegexOptions.Compiled)
        Private _dosSize As Regex = New System.Text.RegularExpressions.Regex("((?<=(\d\d:\d\d\s*(AM|PM|am|pm)\s*))\d+)|(\d+(?=\s+\d\d-\d\d-\d\d\s+))", RegexOptions.Compiled)
        Private _dosDir As Regex = New System.Text.RegularExpressions.Regex("<DIR>|\sDIR\s", RegexOptions.Compiled)


        ''' <summary>
        ''' Method to parse a line of file listing data from the FTP server.
        ''' </summary>
        ''' <param name="line">Line to parse.</param>
        ''' <return>Object representing data in parsed file listing line.</return>
        Public Function ParseLine(line As String) As FtpItem Implements IFtpItemParser.ParseLine
            If _isUnix.IsMatch(line) Then
                Return ParseUnixFormat(line)
            Else
                Return ParseDosFormat(line)
            End If
        End Function

        Private Function ParseUnixFormat(line As String) As FtpItem
            Dim attribs As String = _unixAttribs.Match(line).ToString()
            Dim month As String = _unixMonth.Match(line).ToString()
            Dim day As String = _unixDay.Match(line).ToString()
            Dim year As String = _unixYear.Match(line).ToString()
            Dim time As String = _unixTime.Match(line).ToString()
            Dim size As String = _unixSize.Match(line).ToString()
            Dim name As String = _unixName.Match(line).ToString().Trim()
            Dim symbLink As String = ""

            ' ignore the microsoft 'etc' file that IIS uses for WWW users
            If name = "~ftpsvc~.ckm" Then
                Return Nothing
            End If

            '  if we find a symbolic link then extract the symbolic link first and then
            '  extract the file name portion
            If _unixSymbLink.IsMatch(name) Then
                symbLink = _unixSymbLink.Match(name).ToString()
                name = name.Substring(0, name.IndexOf("->")).Trim()
            End If

            Dim itemType As String = _unixType.Match(line).ToString()


            '  if the current year is not given in unix then we need to figure it out.
            '  basically, if a date is within the past 6 months unix will show the 
            '  time instead of the year
            If year.Length = 0 Then
                Dim curMonth As Integer = DateTime.Today.Month
                Dim curYear As Integer = DateTime.Today.Year

                Dim result As DateTime
                If DateTime.TryParse([String].Format(CultureInfo.InvariantCulture, "1-{0}-2007", month), result) Then
                    If (curMonth - result.Month) < 0 Then
                        year = Convert.ToString(curYear - 1, CultureInfo.InvariantCulture)
                    Else
                        year = curYear.ToString(CultureInfo.InvariantCulture)
                    End If
                End If
            End If

            Dim dateObj As DateTime
            DateTime.TryParse([String].Format(CultureInfo.InvariantCulture, "{0}-{1}-{2} {3}", day, month, year, time), dateObj)

            Dim sizeLng As Long = 0
            Int64.TryParse(size, sizeLng)

            Dim itemTypeObj As FtpItemType = FtpItemType.Other
            Select Case itemType.ToLower(CultureInfo.InvariantCulture)
                Case "l"
                    itemTypeObj = FtpItemType.SymbolicLink
                    Exit Select
                Case "d"
                    itemTypeObj = FtpItemType.Directory
                    Exit Select
                Case "-"
                    itemTypeObj = FtpItemType.File
                    Exit Select
                Case "b"
                    itemTypeObj = FtpItemType.BlockSpecialFile
                    Exit Select
                Case "c"
                    itemTypeObj = FtpItemType.CharacterSpecialFile
                    Exit Select
                Case "p"
                    itemTypeObj = FtpItemType.NamedSocket
                    Exit Select
                Case "s"
                    itemTypeObj = FtpItemType.DomainSocket
                    Exit Select
            End Select

            If name = "." Then
                itemTypeObj = FtpItemType.CurrentDirectory
            End If
            If name = ".." Then
                itemTypeObj = FtpItemType.ParentDirectory
            End If

            If itemTypeObj = FtpItemType.Other OrElse name.Trim().Length = 0 Then
                Return Nothing
            Else
                Return New FtpItem(name, dateObj, sizeLng, itemTypeObj, attribs, FtpUtilities.AttributeToMode(attribs), _
                    symbLink, line)
            End If
        End Function

        Private Function ParseDosFormat(line As String) As FtpItem
            Dim name As String = _dosName.Match(line).ToString().Trim()

            ' if the name has no length the simply stop processing and return null.
            If name.Trim().Length = 0 Then
                Return Nothing
            End If

            Dim [date] As String = _dosDate.Match(line).ToString()
            Dim time As String = _dosTime.Match(line).ToString()
            Dim size As String = _dosSize.Match(line).ToString()
            Dim dir As String = _dosDir.Match(line).ToString().Trim()

            ' put togther the date/time
            Dim dateTime__1 As DateTime = DateTime.MinValue
            DateTime.TryParse([String].Format(CultureInfo.InvariantCulture, "{0} {1}", [date], time), dateTime__1)

            ' parse the file size
            Dim sizeLng As Long = 0
            Int64.TryParse(size, sizeLng)

            ' determine the file item itemType
            Dim itemTypeObj As FtpItemType
            If dir.Length > 0 Then
                itemTypeObj = FtpItemType.Directory
            Else
                itemTypeObj = FtpItemType.File
            End If

            If name = "." Then
                itemTypeObj = FtpItemType.CurrentDirectory
            End If
            If name = ".." Then
                itemTypeObj = FtpItemType.ParentDirectory
            End If

            Return New FtpItem(name, dateTime__1, sizeLng, itemTypeObj, [String].Empty, Nothing, _
                [String].Empty, line)
        End Function

    End Class
End Namespace



